package net.homelinux.chaoswg.io.renga.krypto.datatypes.numerical;

import java.math.BigInteger;

import net.homelinux.chaoswg.io.renga.krypto.datatypes.CryptographicalType;

/**
 * Subtype for numerical types
 * @author martin
 *
 */
public abstract class UnsignedNBit implements CryptographicalType, Comparable<UnsignedNBit> {
    protected BigInteger value = new BigInteger ("0");
    public final BigInteger MIN = new BigInteger ("0");
    
    public UnsignedNBit add (final UnsignedNBit unb) {
        UnsignedNBit toReturn = null;
        
        if (unb.getN () == this.getN ()) {
            toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
            toReturn.setValue ( this.value.add(unb.value).mod(toReturn.getMOD()));
        } else if (unb.getN () > this.getN ()) {
            if (unb.getValue().compareTo(this.getMAX()) < 1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
                toReturn.setValue ( this.value.add(unb.value).mod(toReturn.getMOD()));
            }
        } else if (unb.getN() < this.getN()) {
            if (unb.getValue().compareTo(this.getMAX()) > -1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(unb.getN());
                toReturn.setValue ( this.value.add(unb.value).mod(toReturn.getMOD()));
            }            
        }
        
        return toReturn;
    }
    
    public UnsignedNBit subtract (final UnsignedNBit unb) {
        UnsignedNBit toReturn = null;
        
        if (unb.getN() == this.getN()) {
            toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
            BigInteger tmp = (this.value.subtract(unb.value));
            tmp = ((tmp.signum() >= 0 ? tmp : tmp.add(toReturn.getMOD())));
            toReturn.setValue(tmp);
        } else if (unb.getN () > this.getN ()) {
            if (unb.getValue().compareTo(this.getMAX()) < 1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
                BigInteger tmp = (this.value.subtract(unb.value));
                tmp = ((tmp.signum() >= 0 ? tmp : tmp.add(toReturn.getMOD())));
                toReturn.setValue(tmp);
            }
        } else if (unb.getN() < this.getN()) {
            if (unb.getValue().compareTo(this.getMAX()) > -1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(unb.getN());
                BigInteger tmp = (this.value.subtract(unb.value));
                tmp = ((tmp.signum() >= 0 ? tmp : tmp.add(toReturn.getMOD())));
                toReturn.setValue(tmp);
            }            
        }
        
        return toReturn; 
    }
    
    public UnsignedNBit multiply (final UnsignedNBit unb) {
        UnsignedNBit toReturn = null;
        if (unb.getN() == this.getN()) {
            toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
            toReturn.setValue ( this.value.multiply(unb.getValue()).mod(toReturn.getMOD()));
        } else if (unb.getN () > this.getN ()) {
            if (unb.getValue().compareTo(this.getMAX()) < 1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
                toReturn.setValue ( this.value.multiply(unb.getValue()).mod(toReturn.getMOD()));
            }
        } else if (unb.getN() < this.getN()) {
            if (unb.getValue().compareTo(this.getMAX()) > -1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(unb.getN());
                toReturn.setValue ( this.value.multiply(unb.getValue()).mod(toReturn.getMOD()));
            }            
        }
        
        return toReturn;
    }
    
    public UnsignedNBit exp (final UnsignedNBit unb) {
        UnsignedNBit toReturn = null;

        if (unb.getN() == this.getN()) {
            toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
            toReturn.setValue ( this.value.modPow(unb.value, toReturn.getMOD()));
        } else if (unb.getN () > this.getN ()) {
            if (unb.getValue().compareTo(this.getMAX()) < 1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(this.getN());
                toReturn.setValue ( this.value.modPow(unb.value, toReturn.getMOD()));
            }
        } else if (unb.getN() < this.getN()) {
            if (unb.getValue().compareTo(this.getMAX()) > -1) {
                toReturn =  UnsignedNBitFactory.createUnsignedNBitFactory().get(unb.getN());
                toReturn.setValue ( this.value.modPow(unb.value, toReturn.getMOD()));
            }            
        }

        return toReturn;
    }
    
    public String toString () {
        return this.toHexString();
    }
    
    public String toBInaryString () {
        return value.toString(2);
    }
    
    public String toOctetString () {
        return value.toString(8);
    }
    
    public String toDecimalString () {
        return value.toString(10);
    }
    
    public String toHexString () {
        return value.toString(16);
    }
    
    public BigInteger getValue() {
        return value;
    }

    public void setValue(final BigInteger value) {
        this.value = value;
    }
    
    public int compareTo(final UnsignedNBit o) {
        return this.value.compareTo(o.value);
    }
    
    public boolean equals (final Object obj) {
        return ((obj instanceof UnsignedNBit) && (((UnsignedNBit)obj).value.equals (this.value)));

    }
    
    public abstract BigInteger getMAX ();
    public abstract BigInteger getMOD();
    public abstract int getN ();
}
